package com.example.lotterysystem.service.impl;

import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.RedisUtil;
import com.example.lotterysystem.controller.param.CreateActivityParam;
import com.example.lotterysystem.controller.param.CreatePrizeByActivityParam;
import com.example.lotterysystem.controller.param.CreateUserByActivityParam;
import com.example.lotterysystem.controller.param.PageParam;
import com.example.lotterysystem.mapper.*;
import com.example.lotterysystem.pojo.dataobject.ActivityDO;
import com.example.lotterysystem.pojo.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.pojo.dataobject.ActivityUserDO;
import com.example.lotterysystem.pojo.dataobject.PrizeDO;
import com.example.lotterysystem.service.ActivityService;
import com.example.lotterysystem.service.dto.ActivityDTO;
import com.example.lotterysystem.service.dto.ActivityDetailDTO;
import com.example.lotterysystem.service.dto.CreateActivityDTO;
import com.example.lotterysystem.service.dto.PageListDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class ActivityServiceImpl implements ActivityService {
    private static final Logger logger = LoggerFactory.getLogger(ActivityServiceImpl.class);

    /**
     * 活动缓存前置，为了区分业务
     */
    private final String ACTIVITY_PREFIX = "ACTIVITY_";
    /**
     * 活动缓存过期时间
     * 3天
     */
    private final Long ACTIVITY_TIMEOUT = 60 * 60 * 24 * 3L;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PrizeMapper prizeMapper;

    @Autowired
    private ActivityMapper activityMapper;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private ActivityUserMapper activityUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 创建活动
     * @param param
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 涉及多表
    public CreateActivityDTO createActivity(CreateActivityParam param) {
        // 检查活动信息是否正确
        checkActivityInfo(param);

        // 保存活动信息
        ActivityDO activityDO = new ActivityDO();
        activityDO.setActivityName(param.getActivityName());
        activityDO.setDescription(param.getDescription());
        activityDO.setStatus(ActivityStatusEnum.RUNNING.name()); // 这里得到的就是 "RUNNING"
        activityMapper.insert(activityDO);

        // 保存活动关联的奖品信息
        // 先将 param 中的List<CreatePrizeByActivityParam> 中的信息拿出来
        List<CreatePrizeByActivityParam> prizeParams = param.getActivityPrizeList();
        // 转换成 数据库要存储的类型，即为 ActivityPrizeDO
        List<ActivityPrizeDO> activityPrizeDOList = prizeParams.stream()
                .map(prizeParam -> {
                    ActivityPrizeDO activityPrizeDO = new ActivityPrizeDO();
                    activityPrizeDO.setActivityId(activityDO.getId());
                    activityPrizeDO.setPrizeId(prizeParam.getPrizeId());
                    activityPrizeDO.setPrizeAmount(prizeParam.getPrizeAmount());
                    // 因为在 checkActivityInfo(param); 里面已经判断过奖品等级的有效性了，所以这里可以直接赋值
                    // 不需要进行转换成 枚举类进行可控操作
                    activityPrizeDO.setPrizeTiers(prizeParam.getPrizeTiers());
                    activityPrizeDO.setStatus(ActivityPrizeStatusEnum.INIT.name());
                    return activityPrizeDO;
                }).collect(Collectors.toList());
        // 将其存入到对应的数据表中即可
        activityPrizeMapper.batchInsert(activityPrizeDOList);

        // 保存活动关联的用户信息
        // 先将 param 中的List<CreateUserByActivityParam> 中的信息拿出来
        List<CreateUserByActivityParam> userParams = param.getActivityUserList();
        // 转换成 数据库要存储的类型，即为 ActivityUserDO
        List<ActivityUserDO> activityUserDOList = userParams.stream()
                .map(userParam -> {
                    ActivityUserDO activityUserDO = new ActivityUserDO();
                    activityUserDO.setActivityId(activityDO.getId());
                    activityUserDO.setUserId(userParam.getUserId());
                    activityUserDO.setUserName(userParam.getUserName());
                    activityUserDO.setStatus(ActivityUserStatusEnum.INIT.name());
                    return activityUserDO;
                }).collect(Collectors.toList());
        // 将其存入到对应的数据表中即可
        activityUserMapper.batchInsert(activityUserDOList);

        // 整合完整的活动信息，存放在redis
        // ActivityDetailDTO:活动+奖品(基础信息和其余信息)+人员

        // 先获取奖品基本属性列表
        // 获取需要查询的奖品id
        List<Long> prizeIds = param.getActivityPrizeList().stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .collect(Collectors.toList());
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);

        ActivityDetailDTO detailDTO =
                convertToActivityDetailDTO(activityDO,activityUserDOList,
                        prizeDOList,activityPrizeDOList);
        // 放到Redis中
        cacheActivity(detailDTO);

        // 构造返回
        CreateActivityDTO createActivityDTO = new CreateActivityDTO();
        createActivityDTO.setActivityId(activityDO.getId());
        return createActivityDTO;
    }

    /**
     * 获取活动列表
     * @param param
     * @return
     */
    @Override
    public PageListDTO<ActivityDTO> findActivityList(PageParam param) {
        // 获取总共的活动个数
        int total = activityMapper.count();

        // 获取当前页列表
        List<ActivityDO> activityDOList = activityMapper.selectActivityList(param.offset(),param.getPageSize());
        List<ActivityDTO> activityDTOList = activityDOList.stream()
                .map(activityDO -> {
                    ActivityDTO activityDTO = new ActivityDTO();
                    activityDTO.setActivityId(activityDO.getId());
                    activityDTO.setActivityName(activityDO.getActivityName());
                    activityDTO.setDescription(activityDO.getDescription());
                    activityDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));
                    return activityDTO;
                }).collect(Collectors.toList());
        return new PageListDTO<>(total,activityDTOList);
    }

    /**
     * 获取活动详细属性
     * @param activityId
     * @return
     */
    @Override
    public ActivityDetailDTO getActivityDetail(Long activityId) {
        if (activityId == null) {
            logger.warn("查询活动详细信息失败，activityId为空！");
            return null;
        }
        // 查询 Redis
        ActivityDetailDTO detailDTO = getActivityFromCache(activityId);
        if (detailDTO != null) {
            logger.info("查询活动详细信息成功！detailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO));
            return detailDTO;
        }

        // 到这里说明 redis 中不存在，进行查表操作，并重新缓存Redis
        // 活动表
        ActivityDO activityDO = activityMapper.selectById(activityId);

        // 活动人员关联表
        List<ActivityUserDO> activityUserDOList = activityUserMapper.selectByActivityId(activityId);

        // 活动奖品关联表
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectByActivityId(activityId);

        // 奖品基本信息表
        // 先去获取 活动奖品关联表 中的奖品Id
        List<Long> prizeIds = activityPrizeDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .distinct()
                .collect(Collectors.toList());
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);

        // 整合信息
        detailDTO = convertToActivityDetailDTO(activityDO,activityUserDOList,prizeDOList,activityPrizeDOList);
        // 重新缓存 Redis
        cacheActivity(detailDTO);

        // 返回结果
        return detailDTO;
    }

    /**
     * 缓存活动详细信息（读取表数据 在缓存）
     * @param activityId
     */
    @Override
    public void cacheActivity(Long activityId) {
        if (activityId == null) {
            logger.warn("要缓存的活动id为空！");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_IS_EMPTY);
        }
        // 缓存信息需要将信息整合成 ActivityDetailDTO 这个类型，所以需要借用 convertToActivityDetailDTO 这个方法

        // 查询表数据：活动表、关联奖品、关联人员、奖品信息表
        ActivityDO activityDO = activityMapper.selectById(activityId);
        if (activityDO == null) {
            logger.error("缓存的活动id有误!");
            throw new ServiceException(ServiceErrorCodeConstants.CACHE_ACTIVITY_ID_EMPTY);
        }
        // 活动奖品表
        List<ActivityUserDO> activityUserDOList = activityUserMapper.selectByActivityId(activityId);
        // 活动奖品表
        List<ActivityPrizeDO> activityPrizeDOList = activityPrizeMapper.selectByActivityId(activityId);
        // 奖品表 -> 根据 activityPrizeDOList 的奖品Id，在活动表中查找对应的完整信息
        List<Long> prizeIds = activityPrizeDOList.stream()
                .map(ActivityPrizeDO::getPrizeId)
                .collect(Collectors.toList());
        List<PrizeDO> prizeDOList = prizeMapper.batchSelectByIds(prizeIds);
        cacheActivity(
                convertToActivityDetailDTO(activityDO,
                        activityUserDOList,
                        prizeDOList,
                        activityPrizeDOList)
        );
    }

    /**
     * 缓存完整的活动信息 ActivityDetailDTO
     * @param detailDTO
     */
    private void cacheActivity(ActivityDetailDTO detailDTO) {
        // key: ACTIVITY_12
        // value: ActivityDetailDTO(json)
        if (detailDTO == null || detailDTO.getActivityId() == null) {
            logger.warn("缓存的信息不存在");
            return;
        }

        // 对于缓存完整的活动信息操作来说，如果没有缓存成功的话，不应该进行回滚操作，也就是抛出异常
        // 因为在 数据库表中已经存在完成信息了，如果从Redis中获取信息的时候没有成功的话，那么可以到数据库中进行获取
        // 之后再从新发送给Redis，进行缓存操作，之后再从Redis中获取信息
        try {
            redisUtil.set(ACTIVITY_PREFIX + detailDTO.getActivityId(),
                    JacksonUtil.writeValueAsString(detailDTO),
                    ACTIVITY_TIMEOUT);
        }catch (Exception e) {
            logger.error("缓存活动异常，ActivityDetailDTO={}",
                    JacksonUtil.writeValueAsString(detailDTO),e);
        }
    }

    /**
     * 根据 key 进行获取对应的 value 值
     * @param activityId
     * @return
     */
    private ActivityDetailDTO getActivityFromCache(Long activityId) {
        if (activityId == null) {
            logger.warn("获取缓存活动数据的activityId为空");
            return null;
        }
         try {
             String str = redisUtil.get(ACTIVITY_PREFIX + activityId);
             if (!StringUtils.hasText(str)) {
                 logger.info("获取缓存活动数据的为空 key:{}",ACTIVITY_PREFIX + activityId);
                 return null;
             }
             return JacksonUtil.readValue(str,ActivityDetailDTO.class);
         }catch (Exception e) {
             logger.error("从缓存中获取活动信息异常，key={}", ACTIVITY_PREFIX + activityId, e);
             return null;
         }
    }

    /**
     * 根据基本 DO 整合完整的活动信息 ActivityDetailDTO
     * @param activityDO 活动表
     * @param activityUserDOList 活动人员关联表
     * @param prizeDOList 奖品基本信息表
     * @param activityPrizeDOList 活动奖品关联表
     * @return
     */
    private ActivityDetailDTO convertToActivityDetailDTO(ActivityDO activityDO,
                                                         List<ActivityUserDO> activityUserDOList,
                                                         List<PrizeDO> prizeDOList,
                                                         List<ActivityPrizeDO> activityPrizeDOList) {
        ActivityDetailDTO activityDetailDTO = new ActivityDetailDTO();
        activityDetailDTO.setActivityId(activityDO.getId());
        activityDetailDTO.setActivityName(activityDO.getActivityName());
        activityDetailDTO.setDesc(activityDO.getDescription());
        activityDetailDTO.setStatus(ActivityStatusEnum.forName(activityDO.getStatus()));

        // apDO: {prizeId,prizeAmount,prizeTiers,status},{prizeId,prizeAmount,prizeTiers,status}....
        // prizeDO: {prizeId, name....},{prizeId, name....}....
        // 需要先找到 prizeId 相同的一组才能进行赋值操作
        List<ActivityDetailDTO.PrizeDTO> prizeDTOList = activityPrizeDOList
                .stream()
                .map(apDO -> {
                    ActivityDetailDTO.PrizeDTO prizeDTO = new ActivityDetailDTO.PrizeDTO();
                    prizeDTO.setPrizeId(apDO.getPrizeId());
                    // 只有 apDO 和 prizeDO的 prizedId一致才可以进行设置基础信息
                    // 使用 Optional 可以避免存在空对象
                    Optional<PrizeDO> optionalPrizeDO = prizeDOList.stream()
                    // 进行 过滤 一下对象，只有当 prizeDOList 中的PrizeDO 中的Id 和 apDO 中的Id一致，才能获取其对象
                            .filter(prizeDO -> prizeDO.getId().equals(apDO.getPrizeId()))
                            // 获取第一个过滤的结果
                            .findFirst();
                    optionalPrizeDO.ifPresent(prizeDO -> {
                        prizeDTO.setName(prizeDO.getName());
                        prizeDTO.setDescription(prizeDO.getDescription());
                        prizeDTO.setImageUrl(prizeDO.getImageUrl());
                        prizeDTO.setPrice(prizeDO.getPrice());
                    });
                    prizeDTO.setPrizeAmount(apDO.getPrizeAmount());
                    prizeDTO.setTiers(ActivityPrizeTiersEnum.forName(apDO.getPrizeTiers()));
                    prizeDTO.setStatus(ActivityPrizeStatusEnum.forName(apDO.getStatus()));
                    return prizeDTO;
                }).collect(Collectors.toList());
        activityDetailDTO.setPrizeDTOList(prizeDTOList);

        List<ActivityDetailDTO.UserDTO> userDTOList = activityUserDOList.stream()
                .map(activityUserDO -> {
                    ActivityDetailDTO.UserDTO userDTO = new ActivityDetailDTO.UserDTO();
                    userDTO.setUserId(activityUserDO.getUserId());
                    userDTO.setUserName(activityUserDO.getUserName());
                    userDTO.setStatus(ActivityUserStatusEnum.forName(activityUserDO.getStatus()));
                    return userDTO;
                }).collect(Collectors.toList());
        activityDetailDTO.setUserDTOList(userDTOList);

        return activityDetailDTO;
    }

    /**
     * 检验活动有效性
     * @param param
     */
    private void checkActivityInfo(CreateActivityParam param) {
        if (param == null) {
            throw new ServiceException(ServiceErrorCodeConstants.CREATE_ACTIVITY_INFO_IS_EMPTY);
        }

        // 人员Id在人员表中是否存在
        // 将 param 中的所有 人员Id拿取到放到 List中
        List<Long> userIds = param.getActivityUserList()
                .stream()
                // 只是获取 CreateUserByActivityParam 中的某一个属性，不需要进行全部映射，只需要获取一个属性即可
                .map(CreateUserByActivityParam::getUserId)
                // 去重操作
                .distinct()
                .collect(Collectors.toList());
        // 之后在 人员表(user表)中根据 param 中获取到的 List人员Id 获取到和其Id相同的值，返回人员表中的对应Id
        List<Long> existUserIds = userMapper.selectExistByIds(userIds);
        // 之后两个 List 进行比较得到看是否是全部相等，变相的其实是看人员表中的id是否全部包含 param中的Id
        if (CollectionUtils.isEmpty(existUserIds)) {
            // 为空就进行抛出异常
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_EMPTY);
        }
        // 遍历userIds 如果existUserIds 中不存在对应的Id就抛异常
        userIds.forEach(userId -> {
            if(!existUserIds.contains(userId)) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_USER_EMPTY);
            }
        });

        // 奖品Id在奖品表中是否存在
        // 将 param 中的所有 奖品Id拿取到放到 List中
        List<Long> prizes = param.getActivityPrizeList()
                .stream()
                .map(CreatePrizeByActivityParam::getPrizeId)
                .distinct()
                .collect(Collectors.toList());
        // 之后在 奖品表(prize表)中根据 param 中获取到的 List奖品Id 获取到和其Id相同的值，返回奖品表中的对应Id
        List<Long> existPrizeIds = prizeMapper.selectExistByIds(prizes);
        // 之后两个 List 进行比较得到看是否是全部相等，变相的其实是看奖品表中的id是否全部包含 param中的Id
        if (CollectionUtils.isEmpty(existPrizeIds)) {
            // 为空就进行抛出异常
            throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_EMPTY);
        }
        // 遍历userIds 如果existUserIds 中不存在对应的Id就抛异常
        prizes.forEach(prizeId -> {
            if(!existPrizeIds.contains(prizeId)) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_EMPTY);
            }
        });

        // 人员 数量是否 大于等于 奖品的数量
        // 人员的个数就是 param中List<CreateUserByActivityParam> 的size的个数，就是人员的个数
        int userAmount = param.getActivityUserList().size();
        // 奖品的个数是 param中List<CreatePrizeByActivityParam> 中的每个对象中prizeAmount属性的个数和
        long prizeAmount = param.getActivityPrizeList()
                .stream()
                .mapToLong(CreatePrizeByActivityParam::getPrizeAmount)
                // 使用 sum 操作进行求和操作
                .sum();
        if (userAmount < prizeAmount) {
            // 奖品多余人员，出现错误，进行抛出异常
            throw new ServiceException(ServiceErrorCodeConstants.USER_PRIZE_AMOUNT_ERROR);
        }

        // 校验活动奖品等级有效性
        param.getActivityPrizeList().forEach(prize -> {
            if (ActivityPrizeTiersEnum.forName(prize.getPrizeTiers()) == null) {
                throw new ServiceException(ServiceErrorCodeConstants.ACTIVITY_PRIZE_TIERS_ERROR);
            }
        });
    }
}
